package ru.msu.cmc.sp.oberon2;

import java.io.FileOutputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.List;
import java.util.LinkedList;
import java.util.Scanner;
import java.io.*;
import java.util.regex.*;

import org.antlr.runtime.ANTLRFileStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.CommonTreeNodeStream;

import ru.msu.cmc.sp.oberon2.exceptions.RuntimeError.*;
import ru.msu.cmc.sp.oberon2.exceptions.RecognitionError;
import ru.msu.cmc.sp.oberon2.exceptions.RuntimeError;

public class Oberon2 {

	public static CommonTreeNodeStream nodes;
	public static CommonTree tree;
	public static String filename;
	public static int imported_lines;

	public static boolean debug = false;
	public static boolean check_only = false;
	@Deprecated
	public static boolean fatal_error = false;

	public static CommonTokenStream tokens;
	
	public static int countLines(String filename) throws IOException {
	    LineNumberReader reader  = new LineNumberReader(new FileReader(filename));
	    int cnt = 0;
	    String lineRead = "";
	    while ((lineRead = reader.readLine()) != null) {}

	    cnt = reader.getLineNumber(); 
	    reader.close();
	    return cnt;
	}

	public static void analysis(ANTLRFileStream input, SymbolTable symtab)
			throws Exception {
		TreeConstructionLexer lex = new TreeConstructionLexer(input);
		tokens = new CommonTokenStream(lex);
		InterAdaptor adaptor = new InterAdaptor();
		try {
			TreeConstructionParser parser = new TreeConstructionParser(tokens);
			parser.setTreeAdaptor(adaptor);
			TreeConstructionParser.obmodule_return r = parser.obmodule();
			tree = (CommonTree) r.getTree();
			if (debug) {
				if (tree != null) {
					System.out.println("Initial tree");
					System.out.println(tree.toStringTree());
					MyFrame mf = new MyFrame(tree);
				}
			}
			String prev_tree = "";
			SymbolTable const_symtab = new SymbolTable();
			while (!prev_tree.equals(tree.toStringTree())) {
				prev_tree = tree.toStringTree();
				nodes = new CommonTreeNodeStream(tree);
				ConstCalc calc = new ConstCalc(nodes, const_symtab, filename);
				calc.setTreeAdaptor(adaptor);
				calc.downup(tree);
				nodes.reset();
				const_symtab = new SymbolTable();
				ConstScope cscope = new ConstScope(nodes, const_symtab, filename);
				cscope.setTreeAdaptor(adaptor);
				cscope.downup(tree);
				nodes.reset();
			}
			if (debug) {
				if (tree != null) {
					System.out.println("Tree after const folding");
					System.out.println(tree.toStringTree());
				}
			}
			MainScope ins = new MainScope(nodes, symtab, filename);
			ins.setTreeAdaptor(adaptor);
			ins.downup(tree);
			nodes.reset();
			if (debug) {
				if (tree != null) {
					System.out.println("Tree after scope");
					System.out.println(tree.toStringTree());
				}
			}
			Types typeComp = new Types(nodes, symtab, filename);
			typeComp.setTreeAdaptor(adaptor);
			typeComp.downup(tree);
			nodes.reset();
		} catch (RecognitionError e) {
			fatal_error = true;
			e.print();
			if (debug) {
				e.printStackTrace();
			} else {
				System.exit(100);
			}
		} catch (Exception e) {
			if (debug) {
				e.printStackTrace();
			} else {
				e.printStackTrace();
				System.exit(100);
			}
		}
	}

	public static void print_defs(SymbolTable symtab) {

	}

	public static void run_program(CommonTreeNodeStream nodes, CommonTree tree)
			throws Exception {
		try {
			CommonTreeNodeStream nodes_interpr = new CommonTreeNodeStream(tree);
			nodes_interpr.setTokenStream(tokens);
			Interpreter interpr = new Interpreter(nodes);
			interpr.setTreeAdaptor(new InterAdaptor());
			interpr.program();
		}
		catch (RuntimeError e) {
			String errmsg = "";
			int code = 0;

				if (e.e == RuntimeErrorType.HALT) {
					System.exit(0);
				}

				switch (e.e) {
				case INPUT_FORMAT_ERROR:
					errmsg = "Input format error";
					code = 101;
					break;
				case DIVISION_BY_ZERO:
					errmsg = "Division by zero";
					code = 102;
					break;
				case INTEGER_OVERFLOW:
					errmsg = "Integer overflow";
					code = 103;
					break;
				case DOMAIN_ERROR:
					errmsg = "Domain error";
					code = 104;
					break;
				case OUT_BOUNDS_ERROR:
					errmsg = "Index out of bounds";
					code = 105;
					break;
				case INTERNAL_ERROR_EXECUTE_EXCEPION:
					errmsg = "Attempting to call exec() on expression node!";
					code = 1000;
					break;
				}
				System.err.println(filename + ":" + e.line + ":"
						+ e.charPositionInLine + ":" + errmsg);
				System.exit(code);
			}
		catch (Exception e) {
			fatal_error = true;
			if (debug) {
				e.printStackTrace();
			} else {
				// TODO uncomment before release
				e.printStackTrace();
				System.exit(100);
			}
		}
	}

	public static void main(String[] args) throws Exception {
		SymbolTable symtab = new SymbolTable();
		filename = args[args.length - 1];
		for (int i = 0; i < args.length - 1; ++i) {
			String string = args[i];
			if (string.equals("--check-only")) {
				check_only = true;
			} else if (string.equals("--debug")) {
				debug = true;
			} else if (string.equals("--defs")) {
				print_defs(symtab);
			} else if (string.equals("--uses")) {

			} else if (string.equals(filename)) {
				System.err.println("Unrecognized option");
				return;
			}
		}
		File main = new File(filename);
		imported_lines = countLines(filename);
		File tmp = new File("tmp.txt");
		PrintStream stream = new PrintStream(tmp);
		Scanner scan = new Scanner(main);
		String preImport = "", postImport = "";
		Pattern pattern = Pattern.compile("IMPORT ([^;]+?,)*([^;]+?);",
				Pattern.DOTALL + Pattern.MULTILINE);
		String text = "";
		while (scan.hasNextLine()) {
			text += scan.nextLine() + "\r\n";
		}
		Matcher matcher = pattern.matcher(text);
		if (matcher.find()) {


			int importEnd = matcher.end(0);
			preImport = text.substring(0, importEnd);
			postImport = text.substring(importEnd);
			
			
			matcher = pattern.matcher(text);
			List<String> modules = new LinkedList<String>();
			while (matcher.find() && matcher.group(1) != null) {
				modules.add(text.substring(matcher.start(2), matcher.end(2))
						.replaceAll(" ", ""));
				text = text.substring(0, matcher.start(2) - 1)
						+ text.substring(matcher.end(2));
				matcher = pattern.matcher(text);
			}
			modules.add(text.substring(matcher.start(2), matcher.end(2))
					.replaceAll(" ", ""));
			stream.println(preImport);
			List<String> typesOfStat = new LinkedList<String>();
			typesOfStat.add("END ");
			typesOfStat.add("CONST ");
			for (int i = 0; i < modules.size(); i++) {
				File f = new File("modules//" + modules.get(i) + ".ob2");
				if (f.canRead()) {
					scan = new Scanner(f);
					text = "";
					while (scan.hasNextLine()) {
						text += scan.nextLine() + "\r\n";
					}
					text = text
							.replaceAll("MODULE " + modules.get(i) + ";", "###");
					text = text.replaceAll("END " + modules.get(i) + ".", "###");
					pattern = Pattern.compile("PROCEDURE (.+?)(\\x2A.*?)?;",
							Pattern.DOTALL + Pattern.MULTILINE);
					matcher = pattern.matcher(text);
					int index = 0;
					while (matcher.find(index)) {
						text = text.substring(0, matcher.start(1))
								+ modules.get(i) + "."
								+ text.substring(matcher.start(1), matcher.end(1)) +
								((matcher.group(2)==null)?text.substring(matcher.end(1)):text.substring(matcher.start(2) + 1));
						
						index = matcher.end(1);
						matcher = pattern.matcher(text);
					}
					for (int j = 0; j < typesOfStat.size(); j++) {
					pattern = Pattern.compile(typesOfStat.get(j) + "(.+?);",
							Pattern.DOTALL + Pattern.MULTILINE);
					matcher = pattern.matcher(text);
					index = 0;
					while (matcher.find(index)) {
						text = text.substring(0, matcher.start(1))
								+ modules.get(i) + "."
								+ text.substring(matcher.start(1));
						
						index = matcher.end(1);
						matcher = pattern.matcher(text);
					}
					}
					stream.println(text);
				}
			}
			stream.println(postImport);
		} else {
			stream.println(text);
		}
		imported_lines = countLines("tmp.txt") - imported_lines;
		analysis(new ANTLRFileStream("tmp.txt"), symtab);
		if (!check_only)
			run_program(nodes, tree);
	}
}
